/*
 * idCollector.cpp
 *
 *  Created on: Jan 20, 2010
 *      Author: kevin
 */

#include "idcollector.h"

#include <Poco/Timer.h>
using Poco::TimerCallback;

#include <Poco/Random.h>
using Poco::Random;

#include <iostream>
using namespace std;

#include <Poco/StringTokenizer.h>
using Poco::StringTokenizer;
#include <Poco/Net/SocketAddress.h>
using Poco::Net::SocketAddress;
#include <Poco/Net/DNS.h>
#include <iterator>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <sstream>
using std::istringstream;

#include <cmath>

#define MAX_ID id_type(-1)

idCollector::idCollector(UInt16 defport):
		_timer(new Timer(2000, 0)),
		_ids(),
		_lock(new Semaphore(0, 1)),
		_stopping(false),
		_isset(false),
		_ds(),
		_defport(defport)
{
	cout << "idCollector :: Constructing idCollector with default port " << _defport <<endl;
	_ds.bind(SocketAddress("0.0.0.0", 0));
	/*SocketAddress sock = SocketAddress(Poco::Net::DNS::thisHost().addresses().front().toString(), "42061");
	cout << sock.toString() << endl;
	try{
		_ds.bind(SocketAddress(Poco::Net::DNS::thisHost().addresses().front().toString(), "42061"));
	}catch(Poco::Exception e){
		cout << e.displayText() << endl;
	}*/
	_ds.setReceiveTimeout(Poco::Timespan(1,0));
	cout <<"idCollector :: My address :: " << _ds.address().toString() << endl;
}

idCollector::~idCollector()
{
	cout << "IdCollector :: Destructing idCollector" << endl;
}

idCollector::id_type idCollector::getId()
{
	if(!_isset)
		_lock->wait();
	return _id;
}

idCollector::id_type idCollector::getPrevId()
{
	if(!_isset)
		_lock->wait();
	return _prev;
}

SocketAddress idCollector::getPrevIP()
{
	if(!_isset)
		_lock->wait();
	return _prevIp;
}

idCollector::id_type idCollector::getNextId()
{
	if(!_isset)
		_lock->wait();
	return _next;
}

SocketAddress idCollector::getNextIP()
{
	if(!_isset)
		_lock->wait();
	return _nextIp;
}

UInt16 idCollector::port()
{
	return _ds.address().port();
}

#define MAX_LEN 1500
void idCollector::run()
{
	//cout << "idCollector :: Starting timer" << endl;
	//_timer->start(TimerCallback<idCollector>((*this) , &idCollector::onTimer));
	cout << "idCollector :: Listening for IDs!" << endl;

	while(!_stopping){
		char buffer[MAX_LEN];
		memset(buffer, 0, MAX_LEN);
		SocketAddress sender;
		int len = 0;
		try {
			cout <<"idCollector :: My address :: " << _ds.address().toString() << endl;
			len = _ds.receiveFrom( buffer, MAX_LEN, sender );

			StringTokenizer st(buffer, ":", StringTokenizer::TOK_IGNORE_EMPTY);
			id_type rec_id;
			UInt16 rec_port;
			{
				cout << "idCollector :: Reading ID" << endl;
				istringstream sin(st[0]);
				sin >> rec_id;
			}

			{
				cout << "idCollector :: Reading port" << endl;
				istringstream sin(st[1]);
				sin >> rec_port;
			}
			cout << "idCollector :: Received " << len << "B :: \"" << rec_id << "\" from " << sender.toString() << endl;
			/*
			 * TODO Make the commandqueue system work. At this moment when I push a command in idcollector
			 * the Node will not pop it..
			 */
			SocketAddress nodeAddr(sender.host(), rec_port);
			cout << "idCollector :: Adding #" << rec_id << " (" << nodeAddr.toString() << ")" << endl;
			_ids.push_back(IDMapping(nodeAddr, rec_id));
		} catch (...) {
			cout << "idCollector :: Not Recieving Anything" << endl;
			_stopping = true;
			_ds.close();
		}
	}
	for (vector< IDMapping >::iterator it = _ids.begin(); it != _ids.end(); ++it)
	{
		cout << "idCollector :: ------ " << it->first.toString() << " (" << it->second << ")" << endl;
	}
	CalcId();
	cout << "idCollector :: Stopped listing for IDs..." << endl;
}
#undef MAX_LEN

void idCollector::onTimer( Timer &timer )
{
	_stopping = true;
	cout << "idCollector :: closing socket" << endl;
	_ds.close();
	cout << "idCollector :: Timer expired" << endl;
	CalcId();
	cout<<"idCollector :: Calculated ID :: "<<_id << endl;
}

void idCollector::CalcId()
{
	if( _ids.empty()){
		cout << "idCollector :: Nothing received, generating an ID" << endl;
		//If there are no ID's we pick the first one at random
		Random rand;
		rand.seed();
		_id = rand.next() % MAX_ID;
		_next = _id;
		_nextIp = SocketAddress(Poco::Net::DNS::thisHost().addresses().front(), _defport);
		_prev = _id;
		_prevIp = _nextIp;
	}
	else{
		cout << "idCollector :: Calculating middle ID out of received ID's" << endl;
		id_type a = 0;
		id_type b = 0;
		biggestDifference(a, b);
		_id = calcMiddleId(a, b);
		cout << "idCollector :: Middle ID :: " << _id << endl;
	}
	_isset = true;
	_lock->set();
}

bool idCollector::comp_id (const IDMapping& p1, const IDMapping& p2)
{
	return (p1.second < p2.second);
}

void idCollector::biggestDifference(id_type &ida, id_type &idb )
{
	id_type diff = 0;
	id_type temp;
	sort(_ids.begin(), _ids.end(), &idCollector::comp_id);


	if( _ids.size() == 1){
		//If only one id is present, calculate the difference between Maxid_type and the id
		//int bytes = sizeof(id_type);
		//bytes = bytes * 8;
		ida = _ids.front().second;
		idb = _ids.front().second;
		_prev = _ids.front().second;
		_prevIp = _ids.front().first;
		cout << "idCollector :: new prevIP -> " << _prevIp.toString() << " #" << _prev << endl;
		_next = _ids.front().second;
		_nextIp = _ids.front().first;
		cout << "idCollector :: new nextIP -> " << _nextIp.toString() << " #" << _next << endl;
	}
	else{
		//Calculate the two id's that have the greatest distance inbetween
		vector< vector< IDMapping >::iterator > intervals;
		id_type i = 0;
		_ids.insert(_ids.begin(), IDMapping(_ids.back()));
		for (vector< IDMapping >::iterator iter = _ids.begin()+1; iter != _ids.end();++iter){
			cout << "idCollector :: collected id's :: " << iter->second << endl;
		}

		for (vector< IDMapping >::iterator iter = _ids.begin()+1; iter != _ids.end(); (++iter, ++i))
		{
			vector<IDMapping>::iterator second;
			second = iter-1;
			if (second->second > iter->second)
				temp = (iter->second - second->second + MAX_ID ) % MAX_ID;
			else
				temp = abs(long(iter->second) - long(second->second)) % MAX_ID;
			cout << "IdCollector :: ID Calculation process :: " << temp << endl;
			cout << "IdCollector :: " << (second->second) << " :: "<< (iter->second) << endl;
			if ( temp > diff ){
				ida = (second->second);
				idb = (iter->second);
				cout << "IdCollector :: idA and idB :: " << ida << " :: " << idb << endl;
				diff = temp;
				intervals.clear();
				intervals.push_back( iter );
			}else if ( temp == diff){
			//	idb = (second->second);
			//	ida = (iter->second);
				cout << "IdCollector :: idA and idB :: " << ida << " :: " << idb << endl;
				intervals.push_back( iter );
			}
		}

		if( intervals.size() > 1){
			cout << "IdCollector :: intervals size > 1 " << endl;
			Random rand;
			rand.seed();
			id_type i = rand.next();
			vector < IDMapping >::iterator index = intervals[i % intervals.size()];
			ida = (index-1)->second;
			_prev = (index-1)->second;
			_prevIp = (index-1)->first;
			idb = (index)->second;
			_next = (index)->second;
			_nextIp = (index)->first;
			cout << "idCollector :: new nextIP -> " << _nextIp.toString() << " #" << _next << endl;
			cout << "idCollector :: new prevIP -> " << _prevIp.toString() << " #" << _prev << endl;
		}
		else{
			cout << "IdCollector :: intervals <= 1" << endl;
			ida = (intervals.front() - 1)->second;
			_prev = (intervals.front() - 1)->second;
			_prevIp = (intervals.front() - 1)->first;
			idb = (intervals.front())->second;
			_next = (intervals.front())->second;
			_nextIp = (intervals.front())->first;
			cout << "idCollector :: new nextIP -> " << _nextIp.toString() << " #" << _next << endl;
			cout << "idCollector :: new prevIP -> " << _prevIp.toString() << " #" << _prev << endl;
		}
	}

}

idCollector::id_type idCollector::calcMiddleId( id_type first, id_type second ){
	if (first == second)
		return (first + MAX_ID/2) % MAX_ID;

	id_type temp = abs( long(second) - long(first) ) / 2;
	cout << "idCollector :: temp | first | second :: " << temp << " | " << first << " | " << second << endl;
	return (first + temp) % MAX_ID ;
}

